Skip To Content

Leveraging Kentico Marketing Automation in a GDPR & CCPA World 

Introduction  

I recently published an article demonstrating how to customize Kentico to provide rich marketing content in form response emails. The solution centered on using Kentico’s marketing automation feature. Our customer is delighted with this solution, because they can use email widgets to provide marketing content related to a user's form submission. The solution is working great in a headless architecture, where we provide a Web API to handle all form submissions, and I was excited to publish this solution. You can imagine my surprise when a fellow MVP, Dmitry Bastron, shared a significant roadblock for using this technique in a traditional Kentico implementation. This left me wondering if the only reason the solution worked was because of our headless architecture. We were bypassing much of Kentico’s built-in activity logging components.  Would other customers be limited in how they could leverage Kentico’s marketing automation feature? 

The Challenge 

So, what's the problem? Compliance regulations like Europe’s GDPR and California’s CCPA require web sites to request a visitor’s consent before using cookies. If a Kentico site is set up to use a default cookie level less than 200 (i.e. Visitor), it will not track unique visitors using contact management and activity tracking until they consent to cookies.  However, the technique for using Kentico's email marketing features to respond to online form submissions requires triggering marketing automation with a form submit activity. In short, if a site visitor submits a form with their email address, but does not consent to cookies, a contact will not be created, a form submit activity will not be logged, and marketing automation will not be triggered.  Without cookies, marketing automation cannot be relied upon to send emails. 

The Solution 

Fortunately, Kentico’s robust API provides a clean solution. In my experience, Kentico’s API often solves problems like this one. When confronted with an obstacle, I’m almost always able to overcome it with its extensible API. This is not typical with other platforms. At first, I looked at solving the problem by overriding out-of-the-box Kentico providers and services, but soon discovered it would require overriding several internal classes.  However, Kentico’s global events provided a simple solution.   To solve it, I needed to handle the event that occurred after new form items were submitted, the BizFormItemEvents.Insert.After event.  After the event fires the solution checks to see if the user already consented.  If the user has not and the form has an email address field, the solution ensures there is a contact for the email address and logs the form submit activity. With this solution in place, Kentico reliably runs marketing automation processes, triggered by form submission activities, even if the visitor has not consented to cookies.  Customers can use Kentico’s marketing automation feature to send emails created in the marketing email module. 

Key Ingredients 

The solution is simple, but it takes more than one snippet of code, so I'll break it down by key ingredients here. 

Global event handler 

The first step is to create a custom Kentico module that implements an event handler for when online forms are submitted. The basic structure for creating a custom code-only module to handle the BizFormItemEvents.Insert.After event looks like this: 

using CMS; 

using CMS.DataEngine; 

using CMS.OnlineForms; 

using KenticoCommunity.CookielessFormHandler; 




[assembly: RegisterModule(typeof(CookielessFormModule))] 




namespace KenticoCommunity.CookielessFormHandler 

{ 

    public class CookielessFormModule: Module 

    { 

         public CookielessFormModule() :base("CookielessFormModule") 

        { } 

         protected override void OnInit() 

        { 

            base.OnInit(); 

            BizFormItemEvents.Insert.After += BizFormItemInsertAfter; 

        } 

        public void BizFormItemInsertAfter(object sender, BizFormItemEventArgs args) 

        { 

            // Handle the event here 

        } 

    } 

} 

Cookie level 

When handling the event, the solution needs to check if the visitor has consented to cookies. If so, the solution doesn’t need to continue. Kentico’s built-in components will do the rest. To do this, we use Kentico’s CurrentCookieLevelProvider: 

public bool IsCookieLevelAtLeastVisitor() 

{ 

    ICurrentCookieLevelProvider currentCookieLevelProvider =  

                Service.Resolve<ICurrentCookieLevelProvider>(); 

    return currentCookieLevelProvider.GetCurrentCookieLevel() >= CookieLevel.Visitor; 

} 

Form contact mapping 

If the cookie level is not at least visitor, the solution needs to proceed by checking the form’s configuration to see if it has a field mapped to the Contact’s ContactEmail attribute. If it does and if the user provides a valid email address, the solution can continue (see Mapping fields to contact attributes). 

var bizFormItem = args.Item; 

var dataClass = DataClassInfoProviderBase<DataClassInfoProvider>.GetDataClassInfo(bizFormItem.ClassName); 




// Is the form field mapped to contact email? 

FormFieldInfo emailFormField =  

    new FormInfo(dataClass.ClassContactMapping).ItemsList.OfType<FormFieldInfo>() 

    .FirstOrDefault((FormFieldInfo item) => item.Name == "ContactEmail"); 

if (emailFormField == null) 

{ 

    return; 

} 

// Does the form item include a valid email address? 

string email = bizFormItem.GetStringValue(emailFormField.MappedToField, string.Empty).Trim(); 

if (string.IsNullOrWhiteSpace(email) || (!ValidationHelper.IsEmail(email))) 

{ 

    return; 

} 


Contact lookup 

If the result is a valid email address, the solution continues by getting a contact using the ContactProvider’s GetContactForSubscribing method. Even though this method is designed for creating contacts for newsletter subscriptions, it’s a perfect fit. It checks to see if there’s a contact with the provided email address. If the contact doesn’t exist, it creates one. This will ensure that if a visitor submits more than one form with the same email address, and without accepting cookies, all form submissions will be tied to the same contact. 

// Get a contact for the email, whether new or matching 

var contactProvider = Service.Resolve<IContactProvider>(); 

var contact = contactProvider.GetContactForSubscribing(email); 

Contact update 

After getting the correct contact for the visitor, the solution needs to honor contact mapping by updating the contact with values provided in the form. Kentico’s ContactInfoProvider provides a method for doing this.  Additionally, the form’s DataClassInfo object provides a property to indicate whether or not the marketing team checked “Overwrite existing contact information” in the form’s configuration. 

// Update contact with form data 

ContactInfoProvider.UpdateContactFromExternalData( 

    bizFormItem,  

    dataClass.ClassContactOverwriteEnabled, contact); 

Log the form submit activity 

Finally, the solution can log the activity, by instantiating the FormSubmitActivityInstaller and calling a method on the ActivityLogService. However, this is where it gets interesting. If the solution simply instantiates the FormSubmitActivityInstaller and calls ActivityLogService.Log, cookie consent will still be required. Without it, the activity will be filtered out and not logged.  If the cookie level is not at least visitor level, the following code will not work: 

IActivityLogService activityLogService = Service.Resolve<IActivityLogService>(); 

FormSubmitActivityInitializer activityInitializer =  

                SystemContext.IsCMSRunningAsMainApplication ?  

                new FormSubmitActivityInitializer(bizFormItem, DocumentContext.CurrentDocument) :  

                new FormSubmitActivityInitializer(bizFormItem); 

activityLogService.Log(activityInitializer, CMSHttpContext.Current.Request); 

The reason the above snippet fails to log the form submit activity is because of Kentico’s ContactProcessingCheckerActivityLogFilter. The ActivityLogService uses a set of filters when logging activities. This one checks the cookie level and filters out activities for visitors that have not accepted cookies. Fortunately, the ActivityLogService provides a way to bypass these filters, the LogWithoutModifiersAndFilters method will skip registered activity log filters and activity log modifiers. Great!  However, this surfaces another problem. One of the registered modifiers is responsible for associating activities to a contact and a site. Without the modifier, the ActivityLogService will attempt to log an activity without a ContactID and throw an exception. 

Custom activity initializer wrapper 

This means we need to set the Activity’s ContactID and SiteID ourselves, using an activity initialization wrapper. This component will wrap the FormSubmitActivityInitializer and allow the code to modify the FormSubmitActivity before it’s returned to the ActivityLogService. Here’s an example that will set the activity’s ActivityContactID and ActivitySiteID properties correctly. 

using CMS.Activities; 

namespace KenticoCommunity.CookielessFormHandler 

{ 

    public class CookielessFormActivityInitializerWrapper: ActivityInitializerWrapperBase 

    { 

        private readonly int _contactId; 

        private readonly int _siteId; 

        public CookielessFormActivityInitializerWrapper( 

            IActivityInitializer originalInitializer,  

            int contactId,  

            int siteId) 

            : base(originalInitializer) 

        { 

            _contactId = contactId; 

            _siteId = siteId; 

        } 

        public override void Initialize(IActivityInfo activity) 

        { 

            base.OriginalInitializer.Initialize(activity); 

            activity.ActivityContactID = _contactId; 

            activity.ActivitySiteID = _siteId; 

        } 

    } 

} 

With this custom wrapper, the solution can successfully log the activity even when the visitor has not accepted cookies. It uses Kentico’s out-of-the-box FormSubmitActivityInitializer but wraps it in the custom one so the ContactID and SiteID are set without the use of an activity modifier. Then, the solution calls LogWihtoutModifiersAndFilters so that the activity is not filtered out because of the cookie level. 

var siteService = Service.Resolve<ISiteService>(); 

IActivityLogService activityLogService = Service.Resolve<IActivityLogService>(); 

// Create Kentico's out-of-the-box FormSubmitActivityInitializer 

FormSubmitActivityInitializer activityInitializer =  

                SystemContext.IsCMSRunningAsMainApplication ?  

                new FormSubmitActivityInitializer(bizFormItem, DocumentContext.CurrentDocument) :  

                new FormSubmitActivityInitializer(bizFormItem); 

// Wrap the out-of-the-box initializer with our custom one 

// and provide the ContactID and SiteID 

var cookielessFormActivityInitializerWrapper =  

                new CookielessFormActivityInitializerWrapper( 

                    activityInitializer,  

                    contact.ContactID,  

                    siteService.CurrentSite.SiteID); 

// Log the activity using the method that bypasses registered modifiers and filters 

activityLogService.LogWithoutModifiersAndFilters(cookielessFormActivityInitializerWrapper); 

Closing 

And there it is. With these straightforward ingredients, we can create a custom Kentico module usable in Kentico MVC or Portal Engine models. The module enables sites to respond to form submission events using marketing automation, even when the normally required cookie level is not available. With this solution, sites can handle users who ignore cookie consent notices but are still willing to submit an online form.  With this solution, marketers can use Kentico’s marketing automation to integrate multiple Kentico features together. For example, instead of being limited to the rudimentary email capabilities in the “Auto Responder” tab of online forms, they can send emails authored using the rich template and widget capabilities of Kentico’s email marketing module. 

Overcoming this obstacle, which would otherwise prevent us from using marketing automation without cookie consent, is a great example of Kentico’s best kept secrets: its robust API and thorough extensibility enables tailoring solutions to specific business needs much more than other platforms. So, if you’re planning to customize Kentico but are nervous about obstacles, go for it. Kentico’s API will almost always provide a solution. 

Have questions on how to get the most out of your Kentico implementation? Drop me a line on Twitter (@HeyMikeWills), or view my other articles here.